﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using RaisingForce.Raise.Hardware;
using RaisingForce.Raise.Presentation.Localization;
using RaisingForce.Raise.Presentation.ViewModel;
using RaisingForce.Raise.Presentation.ViewModel.Extensions;
using RaisingForce.Raise.Presentation.ViewModel.Specialized;
using RaisingForce.RaiseHardwareTest.Hardware;
using RaisingForce.RaiseHardwareTest.Properties;
using RaisingForce.RaiseHardwareTest.ViewModel.MainStates;

namespace RaisingForce.RaiseHardwareTest.ViewModel
{
    sealed internal class MainViewModel :
        ViewModelBase,
        ILocalizable
    {
        #region Constructors

        public MainViewModel(
            string title,
            ISettingMeasures usingMeasures)
        {
            Contract.Requires(title != null);
            Contract.Requires(usingMeasures != null);
            this.usingMeasures = usingMeasures;
            this.data = new MainViewModelData(
                () => new MainStatePool(registerDefaultStates: true));
            this.data.SetState(MainStateKeys.Disconnected);
            this.data.StateChanged += (s, e) =>
                {
                    this.updateHardwareSelectorIsEnabled(this.hardwareSelector);
                    this.updateStateProperties();
                    this.OnStateChanged();
                };
            this.SetTitle(title);
        }

        #endregion

        #region Private Declarations

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(this.options != null);
            Contract.Invariant(this.usingMeasures != null);
        }

        #endregion

        #region State Declarations

        private readonly MainViewModelData data;

        public bool IsConnected
        {
            get { return this.data.State.IsConnected; }
        }

        public bool IsConnecting
        {
            get { return this.data.State.IsConnecting; }
        }

        public bool IsDisconnected
        {
            get { return this.data.State.IsDisconnected; }
        }

        public bool IsEngineRunning
        {
            get { return this.data.State.IsEngineRunning; }
        }

        public string StatusText
        {
            get { return this.data.State.StatusText; }
        }

        private void updateStateProperties()
        {
            this.OnPropertyChanged("IsConnected");
            this.OnPropertyChanged("IsDisconnected");
            this.OnPropertyChanged("StatusText");
        }

        public void SetState(string stateKey)
        {
            switch (stateKey)
            {
                case MainStateKeys.Connected:
                case MainStateKeys.Connecting:
                case MainStateKeys.Disconnected:
                case MainStateKeys.Disconnecting:
                    throw new ArgumentException(
                        string.Format(
                            "Cannot perform manual state change to {0}.",
                            stateKey),
                        "stateKey");
                default:
                    this.data.SetState(stateKey);
                    break;
            }
        }

        public void SetDoneState()
        {
            this.data.SetState(this.data.State.DoneStateKey);
        }

        public IRobotHardware Hardware
        {
            get { return this.data.Hardware; }
        }

        public void ConnectHardware()
        {
            HardwareDispatcher.InvokeAsync(this, () =>
                this.data.State.ConnectHardware(this, this.data));
        }

        public void DisconnectHardware()
        {
            HardwareDispatcher.InvokeAsync(this, () =>
                this.data.State.DisconnectHardware(this, this.data),
                exceptionReaction: (vm, ex) =>
                {
                    string caption;
                    string message = HardwareDispatcher
                        .GetExceptionMessage(ex, out caption);
                    vm.AskConfirmation(
                        message,
                        string.Format(Resources.Error_Disconnecting_CaptionTemplate, caption),
                        () => this.data.SetState(MainStateKeys.Disconnected),
                        isErrorDialog: true);
                });
        }

        public void ResetHardware()
        {
            this.data.ResetHardware();
        }

        #endregion

        #region Properties

        private IRobotHardwareOptions options =
            RobotHardwareOptions.Default;

        public IRobotHardwareOptions Options
        {
            get
            {
                Contract.Ensures(Contract.Result<IRobotHardwareOptions>() != null);
                return this.options;
            }
            set
            {
                IRobotHardwareOptions usingValue = value ?? RobotHardwareOptions.Default;
                if (!object.ReferenceEquals(this.options, usingValue))
                {
                    this.options = usingValue;
                    this.OnOptionsChanged();
                    this.OnPropertyChanged("Options");
                }
            }
        }

        private ISettingMeasures usingMeasures;

        public ISettingMeasures UsingMeasures
        {
            get
            {
                Contract.Ensures(Contract.Result<ISettingMeasures>() != null);
                return this.usingMeasures;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("UsingMeasures");
                }
                Contract.EndContractBlock();
                if (this.usingMeasures != value)
                {
                    this.usingMeasures = value;
                    this.OnUsingMeasuresChanged();
                }
            }
        }

        #endregion

        #region StateChanged Event

        public event EventHandler StateChanged;

        private void OnStateChanged()
        {
            this.OnEvent(this.StateChanged);
        }

        #endregion

        #region OptionsChanged Event

        public event EventHandler OptionsChanged;

        private void OnOptionsChanged()
        {
            this.OnEvent(this.OptionsChanged);
        }

        #endregion

        #region UsingMeasuresChanged Event

        public event EventHandler UsingMeasuresChanged;

        private void OnUsingMeasuresChanged()
        {
            this.OnEvent(this.UsingMeasuresChanged);
        }

        #endregion

        #region LanguageUpdated Event

        public event EventHandler LanguageUpdating;

        private void OnLanguageUpdating()
        {
            this.OnEvent(this.LanguageUpdating);
        }

        #endregion

        #region HardwareSelector ViewModel Property

        private HardwareSelectorViewModel hardwareSelector;

        private const string propHardwareSelector = "HardwareSelector";

        public HardwareSelectorViewModel HardwareSelector
        {
            get
            {
                return this.GetNotNullProperty(
                    ref this.hardwareSelector,
                    propHardwareSelector,
                    this.hardwareSelectorInitializer);
            }
            set
            {
                this.SetProperty(
                    ref this.hardwareSelector,
                    value,
                    propHardwareSelector,
                    this.hardwareSelectorDeinitializer,
                    this.hardwareSelectorInitializer);
            }
        }

        private const string propPluginedHardwareFound = "PluginedHardwareFound";

        private bool pluginedHardwareFound = true;  // by default hardware is found

        /// <summary>
        /// Gets value indicating that plugined hardware was found and attached to this process.
        /// Property is true until <c>SetHardwareNotFound()</c> method is not used.
        /// </summary>
        public bool PluginedHardwareFound
        {
            get { return this.pluginedHardwareFound; }
            private set
            {
                this.SetProperty(
                    ref this.pluginedHardwareFound,
                    value,
                    propPluginedHardwareFound);
            }
        }

        private void hardwareSelector_HardwareSelectionChanged(object s, EventArgs e)
        {
            this.ResetHardware();
        }

        private void hardwareSelectorInitializer(
            HardwareSelectorViewModel hardwareSelector)
        {
            if (hardwareSelector != null)
            {
                hardwareSelector.HardwareSelectionChanged +=
                    this.hardwareSelector_HardwareSelectionChanged;
                this.updateHardwareSelectorIsEnabled(hardwareSelector);
            }
        }

        private void hardwareSelectorDeinitializer(
            HardwareSelectorViewModel hardwareSelector)
        {
            if (hardwareSelector != null)
            {
                hardwareSelector.HardwareSelectionChanged -=
                    this.hardwareSelector_HardwareSelectionChanged;
            }
        }

        private void setHardwareSelectorIsEnabled(
            HardwareSelectorViewModel hardwareSelector,
            bool isEnabled)
        {
            // этот метод нужен для оптимизации,
            // чтобы экземпляр HardwareSelector лишний раз не создавался
            if (hardwareSelector != null)
            {
                hardwareSelector.IsEnabled = isEnabled;
            }
        }

        private void updateHardwareSelectorIsEnabled(
            HardwareSelectorViewModel hardwareSelector)
        {
            this.setHardwareSelectorIsEnabled(hardwareSelector, this.IsDisconnected);
        }

        #endregion

        #region ViewModel Properties

        private ObservableCollection<CommandViewModel> commands;

        private const string propCommands = "Commands";

        public ObservableCollection<CommandViewModel> Commands
        {
            get
            {
                Contract.Ensures(Contract.Result<ObservableCollection<CommandViewModel>>() != null);
                return this.GetNotNullProperty(
                    ref this.commands,
                    propCommands);
            }
            set
            {
                this.SetProperty(
                    ref this.commands,
                    value,
                    propCommands);
            }
        }

        private ObservableCollection<LabeledCommandViewModel> labeledCommands;

        private const string propLabeledCommands = "LabeledCommands";

        public ObservableCollection<LabeledCommandViewModel> LabeledCommands
        {
            get
            {
                Contract.Ensures(Contract.Result<ObservableCollection<LabeledCommandViewModel>>() != null);
                return this.GetNotNullProperty(
                    ref this.labeledCommands,
                    propLabeledCommands);
            }
            set
            {
                this.SetProperty(
                    ref this.labeledCommands,
                    value,
                    propLabeledCommands);
            }
        }

        private MainVisibilityViewModel visibilityOptions;

        private const string propVisibilityOptions = "VisibilityOptions";

        public MainVisibilityViewModel VisibilityOptions
        {
            get
            {
                return this.GetNotNullProperty(
                    ref this.visibilityOptions,
                    propVisibilityOptions);
            }
            set
            {
                this.SetProperty(
                    ref this.visibilityOptions,
                    value,
                    propVisibilityOptions);
            }
        }

        private CommandViewModel importOptionsCommand;

        public CommandViewModel ImportOptionsCommand
        {
            get { return this.importOptionsCommand; }
            set
            {
                this.SetProperty(
                    ref this.importOptionsCommand,
                    value,
                    "ImportOptionsCommand");
            }
        }

        private CommandViewModel exportOptionsCommand;

        public CommandViewModel ExportOptionsCommand
        {
            get { return this.exportOptionsCommand; }
            set
            {
                this.SetProperty(
                    ref this.exportOptionsCommand,
                    value,
                    "ExportOptionsCommand");
            }
        }

        private CommandViewModel aboutCommand;

        public CommandViewModel AboutCommand
        {
            get { return this.aboutCommand; }
            set
            {
                this.SetProperty(
                    ref this.aboutCommand,
                    value,
                    "AboutCommand");
            }
        }

        private LanguageSelectorViewModel languageSelector;

        private const string propLanguageSelector = "LanguageSelector";

        public LanguageSelectorViewModel LanguageSelector
        {
            get
            {
                Contract.Ensures(Contract.Result<LanguageSelectorViewModel>() != null);
                return this.GetNotNullProperty(
                    ref this.languageSelector,
                    propLanguageSelector);
            }
            set
            {
                this.SetProperty(
                    ref this.languageSelector,
                    value,
                    propLanguageSelector);
            }
        }

        #endregion

        #region Public Methods

        public void SetTitle(string title)
        {
            Contract.Requires(title != null);
            this.DisplayName = title;
        }

        /// <summary>
        /// Method modifies <c>PluginedHardwareFound</c> property
        /// and adds "Not found" item to <c>HardwareSelector</c>.
        /// </summary>
        public void SetHardwareNotFound()
        {
            HardwareSettingViewModel fakeHardwareSetting =
                HardwareSettingViewModel.CreateFake(
                    getName: () => Resources.Hardware_Fake_Name,
                    getDescription: () => Resources.Hardware_Fake_Description);
            this.HardwareSelector.HardwareItems.Add(fakeHardwareSetting);
            this.HardwareSelector.IsEnabled = false;
            HardwarePluginInfoCollection.Instance
                .AddPluginInfo(HardwarePluginInfo.CreateFake(
                    getAssemblyName: () => Resources.Hardware_Fake_AssemblyName,
                    getVersionString: () => Resources.Hardware_Fake_VersionString));
            this.PluginedHardwareFound = false;
        }

        #endregion

        #region ILocalizable Members

        public void UpdateLocalization()
        {
            this.OnLanguageUpdating();
            this.data.UpdateLocalization();
            this.updateStateProperties();
            this.HardwareSelector.UpdateLocalization();
        }

        #endregion
    }
}
